package intopo.game.impl.givetake;

import intopo.evogame.partnerselection.config.ParsingException;
import java.io.IOException;
import java.io.PrintStream;
import org.w3c.dom.Element;

import intopo.evogame.partnerselection.Cache;
import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import util.xml.*;
import intopo.game.strategy.ConstantStrategyParameter;
import intopo.game.strategy.StrategyParameter;
import intopo.util.Util;
import java.util.Scanner;

/**
 * This class represents a Give-Take player without memory whose strategy
 * is probabilistic. This strategy has 6 parameters divided in two sets:
 * one is used when the player has the resource, while the other when the
 * player does not have it.

 * <p> When the player obtains the resource possession (his opponent give
 * it, he took it, or he started the game with the resource) he will wait
 * {@code noneGiterations} iterations before giving it. Afterwards it may
 * give the resource with probability {@code giveProbability}. If {@code
 * giveIteration} iterations have elapsed, then the player gives the
 * resource.

 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Lopes da Silva Mariano</a>

 * @version 2.0
 */
public class GiveTakeProbabilisticStrategy
	extends GiveTakeStrategy
{
	/**
	 * Constructor tag used in text streams and in the map ADT.
	 */
	static final String CONSTRUCTOR_TAG = "GiveTakeProbabilisticStrategy";
	/**
	 * How much time the player keeps the resource before it may give
	 * it. This field represents the number of iterations that must pass
	 * since the player obtained the resource possession before it may
	 * give. The lowest value is 0 meaning the player gives the resource
	 * rightly after it had had it.

	 * @see #giveIteration */
	protected int noneGiterations;

	/**
	 * The probability of performing the <em>give</em> action. This field is
	 * used when the player has had the resource for {@code noneGiterations}
	 * iterations.

	 * @see #noneGiterations */
	protected float giveProbability;

	/**
	 * How much time the agent keeps the resource before it gives
	 * it. The lowest value is give_time meaning the <em>give</em> action
	 * is deterministic (the <code>give_probability</code> field is not
	 * used).

	 * <p> If this field is <code>numeric_limits<int>::max()</code> then
	 * it never changes . If in this case the
	 * field <code>give_probability</code> is 0.0, then the agent never
	 * gives the resource. */
	protected int giveIteration;

	/**
	 * How much time the player waits without the resource before it may
	 * take it. The lowest value is 0 meaning the player takes the resource
	 * rightly after it had not had it. The highest value is the lower of
	 * <code>MAX_TIME_TO_TAKE</code> or <code>take_time</code>.
	 */
	protected int noneTiterations;

	/**
	 * The probability of performing the <em>take</em> action. This field is
	 * used when the player has not had the resource for <code>nonet</code>
	 * iterations. */
	protected float takeProbability;

	protected int takeIteration;

	public GiveTakeProbabilisticStrategy (int noneg, float givep, int nonet, float takep)
	{
		this (noneg, givep, Integer.MAX_VALUE, nonet, takep, Integer.MAX_VALUE);
	}

	public GiveTakeProbabilisticStrategy (int noneg, float givep, int givet, int nonet, float takep, int taket)
	{
		noneGiterations = noneg;
		giveProbability = givep;
		giveIteration = givet;
		noneTiterations = nonet;
		takeProbability = takep;
		takeIteration = taket;
	}

	/**
	 * Construct a new {@code GiveTakeProbabilisticStrategy} whose fields are
	 * initialized by searching the DOM element for specific child nodes.
	 * <p>The XML document must have the following tags and attributes:
	 * <pre>
	 * <GiveTakeProbabilisticStrategy>
	 * <NoneGive attr />
	 * <GiveProb attr />
	 * <GiveTime attr />
	 * <NoneTake attr />
	 * <TakeProb attr />
	 * <TakeTime attr />
	 * </GiveTakeProbabilisticStrategy>
	 * </pre>
	 * 
	 * <p> This constructor is used by class {@code game.XMLStrategyGenerator}.
	 * 
	 * @param element The XML element that contains the straetegy parameters
	 * @throws util.xml.XMLParseException
	 * @see intopo.game.XMLStrategyGenerator
	 */
	public GiveTakeProbabilisticStrategy (Element element)
		throws XMLParseException
	{
		AttributeParser<Integer> apNone = new AttributeParser<Integer>
			(new CheckExistenceAttribute<Integer> (),
			 new ParseAsRandomInt (),
			 new ParseAsInt (0, Integer.MAX_VALUE));
		AttributeParser<Double> apProb = new AttributeParser<Double>
			(new CheckExistenceAttribute<Double> (),
			 new ParseAsRandomDouble (),
			 new ParseAsDouble (0, 1));
		AttributeParser<Integer> apTime = new AttributeParser<Integer>
			(new DefaultValue<Integer> (Integer.MAX_VALUE),
			 new ParseAsRandomInt (),
			 new ParseAsInt (0, Integer.MAX_VALUE));

		noneGiterations = apNone.parse (element, "NoneGive", "value");
		giveProbability = apProb.parse (element, "GiveProb", "value").floatValue ();
		giveIteration   = apTime.parse (element, "GiveTime", "value");
				
		noneTiterations = apNone.parse (element, "NoneTake", "value");
		takeProbability = apProb.parse (element, "TakeProb", "value").floatValue ();
		takeIteration   = apTime.parse (element, "TakeTime", "value");
	}

	public GiveTakeProbabilisticStrategy (Scanner scanner)
	{
		this (scanner.nextInt (), scanner.nextFloat (), scanner.nextInt (), scanner.nextInt (), scanner.nextFloat (), scanner.nextInt ());
	}

	public GiveTakeStrategy clone ()
	{
		return new GiveTakeProbabilisticStrategy (noneGiterations, giveProbability, giveIteration, noneTiterations, takeProbability, takeIteration);
	}

	public int numberParameters ()
	{
		return 6;
	}

	public void setParameterValue (int parameterIndex, Object value)
	{
		switch (Parameter.values () [parameterIndex]) {
			case NONE_G_ITERATIONS:
				noneGiterations = ((Number) value).intValue ();
				break;
			case GIVE_PROBABILITY:
				giveProbability = ((Number) value).floatValue ();
				break;
			case GIVE_ITERATION:
				giveIteration = ((Number) value).intValue ();
				break;
			case NONE_T_ITERATIONS:
				noneTiterations = ((Number) value).intValue ();
				break;
			case TAKE_PROBABILITY:
				takeProbability = ((Number) value).floatValue ();
				break;
			case TAKE_ITERATION:
				takeIteration = ((Number) value).intValue ();
				break;
		default:
			throw new AssertionError ();
		}
	}

	public Object getParameterValue (int index)
	{
		switch (Parameter.values () [index]) {
			case NONE_G_ITERATIONS:
				return new Integer (noneGiterations);
			case GIVE_PROBABILITY:
				return new Float (giveProbability);
			case GIVE_ITERATION:
				return new Integer (giveIteration);
			case NONE_T_ITERATIONS:
				return new Integer (noneTiterations);
			case TAKE_PROBABILITY:
				return new Float (takeProbability);
			case TAKE_ITERATION:
				return new Integer (takeIteration);
		}
		throw new AssertionError ();
	}

	public String getParameterName (int index)
	{
		return Parameter.values () [index].name;
	}

	public StrategyParameter getParameter (int index)
	{
		switch (Parameter.values () [index]) {
			case NONE_G_ITERATIONS:
				return new ConstantStrategyParameter<Integer> (Parameter.values () [index].name, noneGiterations);
			case GIVE_PROBABILITY:
				return new ConstantStrategyParameter<Float> (Parameter.values () [index].name, giveProbability);
			case GIVE_ITERATION:
				return new ConstantStrategyParameter<Integer> (Parameter.values () [index].name, giveIteration);
			case NONE_T_ITERATIONS:
				return new ConstantStrategyParameter<Integer> (Parameter.values () [index].name, noneTiterations);
			case TAKE_PROBABILITY:
				return new ConstantStrategyParameter<Float> (Parameter.values () [index].name, takeProbability);
			case TAKE_ITERATION:
				return new ConstantStrategyParameter<Integer> (Parameter.values () [index].name, takeIteration);
		}
		throw new AssertionError ();
	}

	public boolean give (GiveTakeHistory gth, int playerIndex)
	{
		int time = gth.getIterationsSinceLastResourceExchange ();
		if (time < noneGiterations)
			return false;
		if (time < giveIteration)
			return Cache.supply.nextFloat () <= giveProbability;
		return true;
	}

	public boolean take (GiveTakeHistory gth, int playerIndex)
	{
		int time = gth.getIterationsSinceLastResourceExchange ();
		if (time < noneTiterations)
			return false;
		if (time < takeIteration)
			return Cache.supply.nextFloat () <= takeProbability;
		return true;
	}
	
	public int averageGive ()
	{
		if (this.giveProbability == 0) {
			return this.giveIteration;
		}
		else {
			int ag = (int) Math.round (this.noneGiterations + 1.0f / this.giveProbability);
			if (ag < this.giveIteration) {
				return ag;
			}
			else {
				return this.giveIteration;
			}
		}
	}
	
	public int averageTake ()
	{
		if (this.takeProbability == 0) {
			return this.takeIteration;
		}
		else {
			int at = (int) Math.round (this.noneTiterations + 1.0f / this.takeProbability);
			if (at < this.takeIteration) {
				return at;
			}
			else {
				return this.takeIteration;
			}
		}
	}

	public GiveTakeStrategy getMutantAllGenes ()
	{
		int noneg;
		float givep;
		int givet;
		int nonet;
		float takep;
		int taket;
		cern.jet.random.Binomial binomial = new cern.jet.random.Binomial (10, 0.5, Cache.supply);
		if (Cache.supply.nextInt () < 0) {
			noneg = Math.max (this.noneGiterations - binomial.nextInt (), 0);
		}
		else {
			noneg = Math.min (this.noneGiterations + binomial.nextInt (), BinParameters.giveIteration);
		}
		if (Cache.supply.nextInt () < 0) {
			nonet = Math.max (this.noneTiterations - binomial.nextInt (), 0);
		}
		else {
			nonet = Math.min (this.noneTiterations + binomial.nextInt (), BinParameters.takeIteration);
		}
		if (Cache.supply.nextInt () < 0) {
			givet = Math.max (this.giveIteration - binomial.nextInt (), noneg);
		}
		else {
			givet = Math.min (Math.max (noneg, this.giveIteration + binomial.nextInt ()), BinParameters.giveIteration);
		}
		if (Cache.supply.nextInt () < 0) {
			taket = Math.max (this.takeIteration - binomial.nextInt (), nonet);
		}
		else {
			taket = Math.min (Math.max (nonet, this.takeIteration + binomial.nextInt ()), BinParameters.takeIteration);
		}
		cern.jet.random.Normal normal = new cern.jet.random.Normal (0.0, 0.1, Cache.supply);
		givep = Math.max (0.0f, Math.min (this.giveProbability + (float) normal.nextDouble (), 1.0f));
		takep = Math.max (0.0f, Math.min (this.takeProbability + (float) normal.nextDouble (), 1.0f));
		return new GiveTakeProbabilisticStrategy (noneg, givep, givet, nonet, takep, taket);
	}

	public GiveTakeStrategy getMutantSingleGene ()
	{
		int noneg;
		float givep;
		int givet;
		int nonet;
		float takep;
		int taket;
		int gene = Util.nextPositiveInt (6);
		noneg = (gene != 0 ? this.noneGiterations : Util.perturbNormal (this.noneGiterations, Parameters.timeStddev, 0, this.giveIteration));
		givep = (gene != 1 ? this.giveProbability : Util.perturbNormal (this.giveProbability, Parameters.probStddev, 0f, 1f));
		givet = (gene != 2 ? this.giveIteration   : Util.perturbNormal (this.giveIteration,   Parameters.timeStddev, this.noneGiterations, Parameters.maxGiveTime));
		nonet = (gene != 3 ? this.noneTiterations : Util.perturbNormal (this.noneTiterations, Parameters.timeStddev, 0, this.takeIteration));
		takep = (gene != 4 ? this.takeProbability : Util.perturbNormal (this.takeProbability, Parameters.probStddev, 0f, 1f));
		taket = (gene != 5 ? this.takeIteration   : Util.perturbNormal (this.takeIteration,   Parameters.timeStddev, this.noneTiterations, Parameters.maxTakeTime));
		return new GiveTakeProbabilisticStrategy (noneg, givep, givet, nonet, takep, taket);
	}

	public int getBin ()
	{
		int bin = 0, r;
		bin += this.noneGiterations;
		bin *= BinParameters.takeIteration + 1;
		bin += this.noneTiterations;
		bin *= BinParameters.giveProbability;
		r = (int) Math.floor (this.giveProbability * BinParameters.giveProbability);
		if (r == BinParameters.giveProbability) {
			r--;
		}
		bin += r;
		bin *= BinParameters.takeProbability;
		r = (int) Math.floor (this.takeProbability * BinParameters.takeProbability);
		if (r == BinParameters.takeProbability) {
			r--;
		}
		bin += r;
		bin *= BinParameters.giveIteration + 1;
		bin += this.giveIteration;
		bin *= BinParameters.takeIteration + 1;
		bin += this.takeIteration;
		return bin;
	}

	static String getBinDescription (int bin)
	{
		throw new UnsupportedOperationException ("Not yet implemented");
	}
	
	public boolean isCooperator ()
	{
		return this.averageGive () < this.averageTake ();
	}

	public void print (java.io.PrintStream ps)
	{
		ps.print (CONSTRUCTOR_TAG);
		ps.print (' ');
		ps.print (noneGiterations);
		ps.print (' ');
		ps.print (giveProbability);
		ps.print (' ');
		ps.print (giveIteration);
		ps.print (' ');
		ps.print (noneTiterations);
		ps.print (' ');
		ps.print (takeProbability);
		ps.print (' ');
		ps.print (takeIteration);
		return ;
	}
	/**
	 * Writes this strategy to the given stream.
	 * <p> The data format is:
	 * <pre>
	 * | byte 1 | byte 2 | byte 3 | byte 4 | byte 5 | byte 6 |
	 * | none G |        give probability           |  give  |
	 * |  time  |                                   |  time  |
	 *
	 * | byte 7 | byte 8 | byte 9 | byte 10| byte 11| byte 12|
	 * | none T |        take probability           |  take  |
	 * |  time  |                                   |  time  |
	 * </pre>
	 * @param dos The stream where the data is written
	 * @throws java.io.IOException
	 */
	public void write (java.io.DataOutputStream dos)
		throws IOException
	{
		dos.writeByte (this.noneGiterations);
		dos.writeFloat (this.giveProbability);
		dos.writeByte (this.giveIteration);
		dos.writeByte (this.noneTiterations);
		dos.writeFloat (this.takeProbability);
		dos.writeByte (this.takeIteration);
	}

	public String toString ()
	{
		StringBuffer sb = new StringBuffer (6 * 10 + 5);
		sb.append (this.noneGiterations);
		sb.append (' ');
		sb.append (this.noneTiterations);
		sb.append (' ');
		sb.append (this.giveProbability);
		sb.append (' ');
		sb.append (this.takeProbability);
		sb.append (' ');
		sb.append (this.giveIteration);
		sb.append (' ');
		sb.append (this.takeIteration);
		return sb.toString ();
	}

	/**
	 * An enumeration that represents {@code GiveTakeProbabilisticStrategy} strategy parameters
	 */
	enum Parameter
	{
		NONE_G_ITERATIONS ("noneg"),
		GIVE_PROBABILITY ("givep"),
		GIVE_ITERATION ("givet"),
		NONE_T_ITERATIONS ("nonet"),
		TAKE_PROBABILITY ("takep"),
		TAKE_ITERATION ("taket");

		/**
		 * The name of the strategy parameter.
		 */
		private String name;

		/**
		 * Sole constructor.

		 * @param name The name of the strategy parameter.
		 */
		private Parameter (String name)
		{
			this.name = name;
		}
	}
	/**
	 * Provides static fields used in calculating the <i>bin</i> of the enclosing strategy.
	 *
	 * <p> Some of the fields also define the maximum value a strategy parameter can have.
	 */
	static public class BinParameters
	{
		static public int giveProbability = 3;
		static public int takeProbability = 3;
		static public int giveIteration = 5;
		static public int takeIteration = 5;
	}
	static public class Parameters
	{
		/**
		 * Maximum value of the {@code noneGIterations} and {@code giveIteration} attributes of class {@code GiveTakeProbabilisticStrategy}.
		 */
		static public int maxGiveTime;
		/**
		 * Maximum value of the {@code noneTIterations} and {@code takeIteration} attributes of class {@code GiveTakeProbabilisticStrategy}.
		 */
		static public int maxTakeTime;
		/**
		 * Standard deviation of the normal distribution used to mutate genes  {@code noneGIterations} and {@code giveIteration}, {@code noneTIterations} and {@code takeIteration}.
		 */
		static public double timeStddev;
		/**
		 * Standard deviation of the normal distribution used to mutate genes  {@code takep} and {@code givep}.
		 */
		static public float probStddev;
		static public void read (Scanner scanner)
			throws ParsingException
		{
			Parameters.maxGiveTime = AbstractTextConfigFile.readInt (scanner, "maximum value of give parameters");
			Parameters.maxTakeTime = AbstractTextConfigFile.readInt (scanner, "Maximum value of take parameters");
			Parameters.probStddev = AbstractTextConfigFile.readFloat (scanner, "standard deviation of normal distribution used to mutate integer parameters");
			Parameters.timeStddev = AbstractTextConfigFile.readFloat (scanner, "Standard deviation of the normal distribution used to mutate probability parameters");
		}

		static void write (PrintStream ps)
		{
			ps.print (Parameters.maxGiveTime);
			ps.println ("\t# maximum value of give parameters");
			ps.print (Parameters.maxTakeTime);
			ps.println ("\t# Maximum value of take parameters");
			ps.print (Parameters.probStddev);
			ps.println ("\t# standard deviation of normal distribution used to mutate integer parameters");
			ps.print (Parameters.timeStddev);
			ps.println ("\t# Standard deviation of the normal distribution used to mutate probability parameters");
		}
	}
}
